const fs = require('fs');
const http = require('http');
const path = require('path');
const Router = require('./lib/router');
const helper = require('./lib/helper');
const Container = require('varal-container');
const Controller = require('./lib/controller');
const Middleware = require('./lib/middleware');
const Application = require('./lib/application');
const EventEmitter = require('events').EventEmitter;

const ROOT_PATH = process.cwd();
const CONFIG_DEFAULT = {
	port: 8888,
	debug: false,
	logPath: 'logs',
	viewPath: 'views',
	staticPath: 'public',
	servicePath: 'services',
	controllerPath: 'controllers',
	rootPath: ROOT_PATH,
};

class Varal extends Container {

	constructor() {
		super();
		this.loadConfig();
		this.loadComponent();
		this.loadErrorHandler();
		this.loadPlugins();
	}

	loadConfig() {
		const config = require(path.join(ROOT_PATH, 'config/config.js'));
		let config_env = {};
		switch (process.env.NODE_ENV) {
			case 'dev':
				config_env = require(path.join(ROOT_PATH, 'config/config.dev.js'));
				break;
			case 'beta':
				config_env = require(path.join(ROOT_PATH, 'config/config.beta.js'));
				break;
			case 'production':
				config_env = require(path.join(ROOT_PATH, 'config/config.prod.js'));
				break;
		}
		Object.assign(config, config_env);
		this.config = Object.assign(CONFIG_DEFAULT, config);
	}

	loadComponent() {
		this.router = new Router();
		this.middleware = new Middleware();
		this.emitter = new EventEmitter();
	}

	loadErrorHandler() {
		process.on('uncaughtException', err => {
			this.log('UncaughtException', err.stack || err);
			process.exit(1);
		});
		this.on('error', (err, exit) => {
			this.log('Error', err.stack || err);
			if (exit)
				process.exit(1);
		});
	}

	loadPlugins() {
		if (Array.isArray(this.config.plugins))
			for (let plugin of this.config.plugins)
				if (typeof plugin.name === 'string' && typeof plugin.concrete === 'function')
					if (plugin.singleton === true)
						this.singleton(plugin.name, plugin.concrete);
					else
						this.bind(plugin.name, plugin.concrete);
	}

	loadFile(filePath) {
		const callback = require(path.join(this.config.rootPath, filePath));
		if (typeof callback === 'function')
			callback(this);
	}

	loadPath(dirPath) {
		const realPath = path.join(this.config.rootPath, dirPath);
		if (!fs.existsSync(realPath))
			return;
		const routes = fs.readdirSync(realPath);
		for (let i = 0; i < routes.length; i += 1)
			this.loadFile(path.join(dirPath, routes[i]));
	}

	log(type, content) {
		const filePath = path.join(this.config.rootPath, this.config.logPath);
		if (!fs.existsSync(filePath)) {
			fs.mkdirSync(filePath);
		}
		const file = path.join(filePath, helper.date('Y-m-d') + '.log');
		content = `[${helper.date()}][${type}] ${content}\n`;
		fs.appendFileSync(file, content);
	}

	use(middleware) {
		if (Array.isArray(middleware))
			this.middleware.globalMiddleware = helper.arrayMerge(this.middleware.globalMiddleware, middleware);
		else if (typeof middleware === 'string')
			this.middleware.globalMiddleware.push(middleware);
		else if (typeof middleware === 'function')
			middleware(this);
	}

	get(path, callback) {
		return this.router.defaultGroup.get(path, callback);
	}

	post(path, callback) {
		return this.router.defaultGroup.post(path, callback);
	}

	any(path, callback) {
		return this.router.defaultGroup.add('ANY', path, callback);
	}

	route(method, path, callback) {
		return this.router.defaultGroup.add(method, path, callback);
	}

	add(name, callback, priority) {
		return this.middleware.add(name, callback, priority);
	}

	group(options, callback) {
		return this.router.defaultGroup.group(options, callback);
	}

	on(eventName, listener) {
		return this.emitter.on(eventName, listener);
	}

	run() {
		const self = this;
		http.createServer((request, response) => {
			const app = new Application(self, request, response);
			app.handle().catch(err => {
				app.error(err);
			});
		}).listen(this.config.port);
	}

}

Varal.Controller = Controller;

exports = module.exports = Varal;
